package com.stars.easyms.datasource.transaction;

import com.stars.easyms.datasource.EasyMsDataSource;
import com.stars.easyms.datasource.EasyMsMasterSlaveDataSource;
import org.springframework.jdbc.datasource.ConnectionHolder;
import org.springframework.lang.NonNull;
import org.springframework.lang.Nullable;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.interceptor.TransactionAttribute;
import org.springframework.util.Assert;

import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;

/**
 * <p>className: EasyMsTransactionManager</p>
 * <p>description: EasyMs自定义事务信息类</p>
 *
 * @author guoguifang
 * @date 2019-12-01 21:22
 * @since 1.4.2
 */
final class EasyMsTransactionManager {

    @NonNull
    private final EasyMsDataSourceTransactionManager dataSourceTransactionManager;

    @Nullable
    private TransactionDefinition transactionDefinition;

    private boolean annotationTransaction;

    @Nullable
    private String joinPointIdentification;

    @Nullable
    private String currentTransactionName;

    @Nullable
    private Boolean currentTransactionReadOnly;

    @Nullable
    private Integer currentTransactionIsolationLevel;

    @Nullable
    private Boolean actualTransactionActive;

    @Nullable
    private Boolean joinPreviousTransaction;

    @Nullable
    private Boolean nestedAndUseSavepoint;

    private boolean rollbackOnly;

    @NonNull
    private final Map<EasyMsMasterSlaveDataSource, EasyMsDataSource> fixedEasyMsDataSourceMap;

    @NonNull
    private final Map<EasyMsDataSource, EasyMsTransactionStatusHolder> transactionStatusHolderMap;

    @Nullable
    private EasyMsTransactionManager previousTransactionManager;

    EasyMsTransactionManager(@NonNull EasyMsDataSourceTransactionManager dataSourceTransactionManager, @Nullable TransactionAttribute transactionAttribute,
                             @NonNull String joinPointIdentification) {
        this(dataSourceTransactionManager, transactionAttribute);
        this.joinPointIdentification = joinPointIdentification;
        this.annotationTransaction = true;
    }

    EasyMsTransactionManager(@NonNull EasyMsDataSourceTransactionManager dataSourceTransactionManager, @Nullable TransactionDefinition transactionDefinition) {
        this.dataSourceTransactionManager = dataSourceTransactionManager;
        this.fixedEasyMsDataSourceMap = new HashMap<>(8);
        this.transactionStatusHolderMap = new LinkedHashMap<>();
        this.previousTransactionManager = EasyMsTransactionSynchronizationManager.getTransactionManager();
        this.transactionDefinition = transactionDefinition;
        if (transactionDefinition != null) {
            this.currentTransactionIsolationLevel = transactionDefinition.getIsolationLevel() != TransactionDefinition.ISOLATION_DEFAULT ?
                    transactionDefinition.getIsolationLevel() : null;
            this.currentTransactionReadOnly = transactionDefinition.isReadOnly();
            this.currentTransactionName = transactionDefinition.getName();
            this.joinPreviousTransaction = isExistPreviousTransaction() && dataSourceTransactionManager.isJoinPreviousTransaction(transactionDefinition);
            this.actualTransactionActive = dataSourceTransactionManager.isActualTransactionActive(
                    transactionDefinition, this.previousTransactionManager, this.joinPreviousTransaction);
            this.nestedAndUseSavepoint = isExistPreviousTransaction() && dataSourceTransactionManager.isNestedAndUseSavepoint(transactionDefinition);
        }

        // 即使没有创建一个新的事务也总是将TransactionInfo绑定到线程，这保证了即使没有创建任何事务，TransactionInfo堆栈也能得到正确的管理。
        EasyMsTransactionSynchronizationManager.setTransactionManager(this);
    }


    @NonNull
    EasyMsDataSource getFixedMasterEasyMsDataSource(@NonNull EasyMsMasterSlaveDataSource easyMsMasterSlaveDataSource) {
        return this.fixedEasyMsDataSourceMap.computeIfAbsent(easyMsMasterSlaveDataSource, e -> {
            EasyMsDataSource easyMsDataSource = null;
            EasyMsTransactionManager localPreviousTransactionManager = this.previousTransactionManager;
            while (localPreviousTransactionManager != null && easyMsDataSource == null) {
                easyMsDataSource = localPreviousTransactionManager.fixedEasyMsDataSourceMap.get(e);
                localPreviousTransactionManager = localPreviousTransactionManager.previousTransactionManager;
            }
            if (easyMsDataSource == null) {
                easyMsDataSource = e.getMasterDataSource();
            }
            return easyMsDataSource;
        });
    }

    @NonNull
    EasyMsTransactionStatusHolder newTransactionStatusHolder(@NonNull EasyMsDataSource easyMsDataSource) {
        Assert.notNull(this.transactionDefinition, "Easy-ms transaction is not active");
        EasyMsTransactionStatusHolder transactionStatusHolder = this.transactionStatusHolderMap.get(easyMsDataSource);
        if (transactionStatusHolder == null && this.previousTransactionManager != null) {
            transactionStatusHolder = getTopTransactionManager().transactionStatusHolderMap.get(easyMsDataSource);
        }
        if (transactionStatusHolder == null) {
            transactionStatusHolder = new EasyMsTransactionStatusHolder();
            this.transactionStatusHolderMap.put(easyMsDataSource, transactionStatusHolder);
            transactionStatusHolder.setTransactionStatus(
                    this.dataSourceTransactionManager.getTransactionStatus(
                            this.transactionDefinition, isExistPreviousTransaction(), easyMsDataSource, this));
        } else if (transactionStatusHolder.getTransactionStatus() == null && this.isActualTransactionActive()) {
            transactionStatusHolder.setTransactionStatus(
                    this.dataSourceTransactionManager.getTransactionStatus(
                            this.transactionDefinition, isExistPreviousTransaction(), easyMsDataSource, this));
        }
        return transactionStatusHolder;
    }

    void bindConnectionHolder(@NonNull EasyMsDataSource easyMsDataSource, @NonNull ConnectionHolder connectionHolder) {
        EasyMsTransactionStatusHolder transactionStatusHolder = this.transactionStatusHolderMap.get(easyMsDataSource);
        Assert.notNull(transactionStatusHolder, "Easy-ms transaction synchronization is not active");
        transactionStatusHolder.setConnectionHolder(connectionHolder);
    }

    void restoreThreadLocalStatus() {
        if (this.previousTransactionManager != null) {
            this.previousTransactionManager.rollbackOnly = this.rollbackOnly;
            EasyMsTransactionSynchronizationManager.setTransactionManager(this.previousTransactionManager);
        } else {
            EasyMsTransactionSynchronizationManager.clearTransactionManager();
        }
    }

    boolean hasTransactionStatus() {
        return this.transactionDefinition != null;
    }

    boolean isActualTransactionActive() {
        return Boolean.TRUE.equals(this.actualTransactionActive);
    }

    @Nullable
    TransactionAttribute getTransactionAttribute() {
        Assert.isTrue(annotationTransaction, "Current transaction is not annotated");
        return (TransactionAttribute) this.transactionDefinition;
    }

    @Nullable
    String getJoinPointIdentification() {
        Assert.isTrue(annotationTransaction, "Current transaction is not annotated");
        return this.joinPointIdentification;
    }

    @NonNull
    Map<EasyMsDataSource, EasyMsTransactionStatusHolder> getTransactionStatusHolderMap() {
        return this.transactionStatusHolderMap;
    }

    @Nullable
    String getCurrentTransactionName() {
        return currentTransactionName;
    }

    boolean isCurrentTransactionReadOnly() {
        return Boolean.TRUE.equals(currentTransactionReadOnly);
    }

    @Nullable
    Integer getCurrentTransactionIsolationLevel() {
        return currentTransactionIsolationLevel;
    }

    boolean isNestedAndUseSavepoint() {
        return Boolean.TRUE.equals(nestedAndUseSavepoint);
    }

    @Nullable
    EasyMsTransactionStatusHolder getTransactionStatusHolder(@NonNull EasyMsDataSource easyMsDataSource) {
        return this.transactionStatusHolderMap.get(easyMsDataSource);
    }

    void setRollbackOnly() {
        this.rollbackOnly = true;
    }

    boolean isRollbackOnly() {
        return this.rollbackOnly;
    }

    @NonNull
    EasyMsTransactionManager getTopTransactionManager() {
        // 获取具体的事务管理，如果本层事务需要加入到上层事务，则无限循环获取到不需要加入上层事务的那一层
        Assert.notNull(this.previousTransactionManager, "Previous transaction does not exist");
        EasyMsTransactionManager localPreviousTransactionManager = this.previousTransactionManager;
        while (localPreviousTransactionManager.isJoinPreviousTransaction()) {
            Assert.notNull(localPreviousTransactionManager.previousTransactionManager, "Previous transaction does not exist");
            localPreviousTransactionManager = localPreviousTransactionManager.previousTransactionManager;
        }
        return localPreviousTransactionManager;
    }

    boolean isJoinPreviousTransaction() {
        return Boolean.TRUE.equals(joinPreviousTransaction);
    }

    boolean isExistPreviousTransaction() {
        return this.previousTransactionManager != null && this.previousTransactionManager.isActualTransactionActive();
    }

    @Override
    public String toString() {
        return (this.transactionDefinition != null ? this.transactionDefinition.toString() : "No transaction");
    }

}